/**
 * jQuery.deck.js
 * Copyright (c) 2010 Craig Hoover - crh3675(at)gmail(dot)com | http://standardscompliance.com
 * Dual licensed under MIT and GPL.
 * Date: 7/3/2010
 * @author Craig Hoover
 * @version 0.6
 * 
 * http://code.google.com/p/jquery-deck-viewer/
 */
var Deck = function(element,options)
{
	this.settings = 
	{
		stackCount:'auto',
		deckClass:'deck',
		stackClass:'stack',
		cardClass:'card',
		easing:'linear',
		baseZ: 100,
		cardNodes:'li',
		previewScale:1.25,
		verticalAlign:'center',
		stackSpacing:150,
		overlayOpacity:0.45,
		cardOffset:3,
		peekaBoo:15,
		fadeBack:true
	};
	
	// create temp card to get dimensions
	var temp = $('<div class="'+this.settings.cardClass+'" style="visibility:hidden">');
	$(element).append(temp);
	var cOw = temp.outerWidth(), cOh =  temp.outerHeight(),cw = temp.width(), ch =  temp.height();
	temp.remove();
	
	var nodes = $(this.settings.cardNodes, this._element);

	$.extend(this.settings, options);				

	// set default props
	this._element = element.get(0);
	this._stacks = Array(); 
	this._cards = nodes;
	this._cardCount = nodes.length;
	this._cardOuterWidth = cOw;
	this._cardOuterHeight = cOh;
	this._cardWidth = cw;
	this._cardHeight = ch;
	this._deckWidth = $(element).innerWidth();
	this._deckHeight = $(element).innerHeight();

	// reset position of our main element
	$(element).css({position:'relative',overflow:'hidden',zIndex:this.settings.baseZ});
	
	var deck = $('<section>').css({
		display:'block',
		width:this._deckWidth,
		height:this._deckHeight,
		zIndex:this.settings.baseZ,
		position:'relative',
		overflow:'hidden'
	}).addClass(this.settings.deckClass);	
	
	this._pseudo = deck.get(0);			
	this._buildStacks(this.settings.stackCount); 	
	$(this._element).append(this._pseudo).children('ul').hide();
	this._scrollToStack();

};

Deck.prototype._buildStacks = function(stackCount)
{
	// check if setting is auto or numeric
	if(typeof stackCount == 'string' && stackCount.match(/auto/i))
	{
		var nodes = $(this.settings.cardNodes, this._element);
		this.settings.stackCount = Math.floor(this._deckWidth / (this._cardOuterWidth));
	}
	
	// get the count of cards per stack
	var count = Math.ceil(this._cardCount / this.settings.stackCount);
	
	// reset deck width using calculated card settings
	var newWidth = (this._cardOuterWidth + (count * this.settings.cardOffset) + this.settings.stackSpacing)  * this.settings.stackCount;
	this._deckWidth = newWidth/this.settings.stackCount + newWidth;
	
	$(this._pseudo).css({width:this._deckWidth});

	// create and append our stacks
	for(i=0;i<this.settings.stackCount;i++)
	{
		start = Math.ceil((this._cardCount/this.settings.stackCount) * i);
		this._stacks.push( new Stack(this, i, start, count, this.settings.stackSpacing) );
	}		
};

Deck.prototype._scrollToStack = function(which)
{	
	// scroll to stack, default is middle one
	var which = typeof which =='undefined' ? Math.floor(this._stacks.length/2) : which;
	
	$(this._stacks).each(function(){
		this._element._active = false;
		$(this._element).children('.stack-overlay').css({display:'block'});
	});
	
	var stack = this._stacks[which];
	var offLeft = (stack._element.offsetLeft + this._cardOuterWidth/2) - this._element.offsetWidth/2;
	
	$(this._element).animate({scrollLeft:offLeft},350, this.settings.easing,function(){
		$(stack._element).children('.stack-overlay').css({display:'none'});
		stack._element._active = true;
	});
	
};


var Stack = function(deck, stackIndex, start, count, spacing)
{
	this._deck = deck;
	this._stackIndex = stackIndex;
	this._cards = Array(); 
	this._basez = (stackIndex + 1) * this._deck.settings.baseZ;
	this._active = false;

	var element = $('<section>').css({display:'block'}).addClass(this._deck.settings.stackClass);
	var width = this._deck._cardOuterWidth;
	var height = this._deck._cardOuterHeight;		
	var posleft = parseFloat((((width + spacing) * stackIndex) + spacing/2) + (this._deck._element.offsetWidth/this._deck.settings.stackCount));
	var postop = 0;
	
	// position our stacks according to settings
	switch(this._deck.settings.verticalAlign)
	{
		case 'bottom': postop = this._deck._deckHeight - height; break;
		case 'top': postop = 0; break;
		case 'center': default: postop = this._deck._deckHeight/2 - (height/2); break;
	}
	
	element.css({position:'absolute',zIndex:this._basez,width:width,height:height,top:postop,left:posleft});
	
	$(this._deck._pseudo).append(element);
	this._pseudo = element.get(0);
	
	var x = 0;

	// add cards to deck
	for(j=start;j<start+count;j++)
	{
		if(deck._cards.get(j))
		{
			this._cards.push( new Card(deck, this, deck._cards.get(j), x));
		}
		x++;
	}	 
	
	this._element = this._pseudo;		
	$.extend(this._element, this);
	
	var newWidth = parseInt($(this._finalCard).parent().css('left')) + this._deck._cardOuterWidth;
	var overlay = $('<div>').addClass('stack-overlay').css({position:'absolute',zIndex:this._basez + 1,top:0,left:0,opacity:this._deck.settings.overlayOpacity,height:height,width:newWidth});
	$(this._element).prepend(overlay).bind('mousedown', Stack._selectStack);

	if(window.console) console.log('stack '+stackIndex + ' has ' + this._cards.length +' items');
};	

Stack._selectStack = function(e)
{
	var element = $(this).get(0);	
	var deck = element._deck;
	
	if(!element._active)
	{
		deck._scrollToStack(this._stackIndex);
	}
};	


var Card = function(deck, stack, element, cardIndex)
{
	this._deck = deck;
	this._stack = stack;
	this._cardIndex = cardIndex;
	this._element = element;
	this._basez = this._stack._basez - cardIndex;

	var postop = 0, posleft = 0;
	var opaq = this._deck.settings.fadeBack ? parseFloat(1 - (cardIndex * .06)) : 1;
	
	if(!isNaN(this._deck.settings.cardOffset))
	{
		postop = cardIndex * this._deck.settings.cardOffset;
		posleft = cardIndex * this._deck.settings.cardOffset;
	}
	
	var size = parseFloat(1 - (this._cardIndex * .035));
	var scale = [size, size];
	var posleft = Math.floor((this._deck._cardOuterWidth - (this._deck._cardOuterWidth * size))) + (this._deck.settings.cardOffset * this._cardIndex) - this._cardIndex;
	var origin = [this._deck._cardOuterWidth,this._deck._cardOuterHeight/2];		
	var holder = $('<div>').addClass('card-holder').css({zIndex:this._basez,width:this._deck._cardOuterWidth,height:this._deck._cardOuterHeight,top:0,left:posleft,position: 'absolute', overflow: 'visible'});
	var element = $('<div>').attr({id:'card-'+stack._stackIndex+'-'+cardIndex})
		.css({opacity:opaq,cursor:'pointer',position:'absolute',zIndex:this._basez,width:this._deck._cardWidth,height:this._deck._cardHeight,top:0,left:0})
		.addClass(this._deck.settings.cardClass).append(this._element.innerHTML)
		.transform({'origin':origin,'scale':scale});
	
	this._origin = origin;
	this._scale = scale;
	this._left = posleft;
	this._card = element.get(0);
	this._stack._finalCard = this._card;
	this._opaq = opaq;
	
	$.extend(this._card, this);	
	
	this._addEvents();
	
	$(this._stack._pseudo).append(holder.append(element));
	this._pseudo = element.get(0);
};

Card._showPreview = function(e)
{
	var element = $(this).get(0);
	if(element._cardIndex == 0)
	{
		var origin = [element.offsetWidth/2,element.offsetHeight/2];			
		$(element).transform({'origin':origin}).animate({scale:element._deck.settings.previewScale + ' ' + element._deck.settings.previewScale},75);
	}
	else
	{
		return;
	}			
};

Card._hidePreview = function(e)
{
	var element = $(this).get(0);
	
	if(element._cardIndex == 0)
	{
		$(element).animate({scale:element._scale.join(' ')},75);
	}
	else
	{
		return;
	}			
};


Card._moveToFront = function(e)
{
	var element = $(this).get(0), holder = $(this).parent(), curIndex = element._cardIndex;		
	var removing = curIndex, newstack = Array();
	
	if(curIndex == 0) return;

	element._stack._moving = true;				
	element._stack._clone = Array();
	
	// remove events from cards, reposition left and create clones
	$(element._stack._cards).each(function(){
		this._removeEvents();
		$(this._card).parent().css({left: this._left});
		element._stack._clone.push( $.extend(true, {}, this) );
	});
	
	// cousins are the cards from the end that have the styles
	// for the front cards we are moving
	var cousins = element._stack._clone.slice(-1 * removing);
			
	// stop any events from occuring
	e.stopImmediatePropagation();			
		
	// move front cards to back
	for(var j=0;j<curIndex;j++)
	{
		var card = 	element._stack._cards[j];
		var copy = cousins[j];	

		(function(copy,card){
			var holder = $(card._card).parent();
			var left = (parseFloat(holder.css('left')) - card._card.offsetWidth) + 'px';

			holder.css({zIndex:copy._basez}).animate({'left':left},250).animate({left:copy._left},250);

			$(card._card).css({zIndex:copy._basez,opacity:1}).transform({origin:copy._origin}).animate({scale:copy._scale.join(' '),opacity:copy._opaq},1000,function(){
				with(card)
				{
					_origin = copy._origin;
					_scale = copy._scale;
					_left = copy._left;
					_cardIndex = copy._cardIndex;
					_opaq = copy._opaq;
					_basez = copy._basez;
				};	
			
				newstack[card._cardIndex] = card;
			
				$.extend(card._card, card);					
			});
			
		})(copy,card);				
	}

	// move back cards to front
	for(var j=curIndex;j<element._stack._cards.length;j++)
	{
		var card = 	element._stack._cards[j];
		var copy = element._stack._clone[j - removing];	

		(function(copy,card,j){
			var holder = $(card._card).parent();
			holder.css({zIndex:copy._basez}).animate({left:copy._left},100);
			$(card._card).css({zIndex:copy._basez}).transform({origin:copy._origin}).animate({scale:copy._scale.join(' ')},100,function(){
				// append new styles to card object
				with(card)
				{
					_origin = copy._origin;
					_scale = copy._scale;
					_left = copy._left;
					_cardIndex = copy._cardIndex;
					_opaq = copy._opaq;
					_basez = copy._basez;						
				};
				
				// re-extend card with new settings
				$.extend(card._card, card);
				
				// append to new stack
				newstack[card._cardIndex] = card;
			
				// if last element has been moved
				if(j == card._stack._cards.length-1)
				{
					card._stack._moving = false;
					card._stack._cards = newstack;
					$(card._stack._cards).each(function(){
						this._addEvents(); // re-add our events
					});
				}
			});
		})(copy,card,j);				
	}		
		
};

Card._peekaBoo = function(e)
{
	var element = $(this).get(0);		
	if(element._stack._moving) return;
	if(element._cardIndex == 0)
	{
		Card._showPreview.call(element,e);			
		return;
	}
	
	try{ e.stopPropagation(); }catch(e){};
	
	$(this).parent().animate({left: element._left + element._deck.settings.peekaBoo},100);
};

Card._peekaBack = function(e)
{
	var element = $(this).get(0);		
	if(element._stack._moving) return;		
	if(element._cardIndex == 0)
	{
		Card._hidePreview.call(element,e);			
		return;
	}
	
	try{ e.stopPropagation(); } catch(e){};
	
	$(this).parent().animate({left: element._left},100);
};

Card.prototype._removeEvents = function()
{
	$(this).unbind('mouseover').unbind('mouseout');
};

Card.prototype._addEvents = function()
{
	var cfg = {
		over: Card._peekaBoo,
		out: Card._peekaBack,
		timeout:100,
      sensitivity:7,
      interval:50
	};
	
	$(this._card).hoverIntent(cfg).click(Card._moveToFront);		
};	


$.fn.createDeck = function(options)
{  
	var element = $(this).get(0);
	element.deck = new Deck(this,options);
};

$.fn.getDeck = function()
{
	var element = $(this).get(0);
	return element.deck;
};